﻿using Microsoft.Win32;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Windows.Input;
using System.Workflow.Activities.Rules;
using System.Workflow.Activities.Rules.Design;
using System.Workflow.ComponentModel.Serialization;
using System.Xml;
using System.Xml.Serialization;
using ViewMaker;
using ViewMaker.Core;
using ViewMaker.Core.Controls;

namespace DbStreamConverter
{
    [ViewProperty(PanelViewControl.Properties.HeaderWidth,150)]
    public class MappingVM : ViewModelBase
    {
        internal const string SourceProperty = "S";
        internal const string BufferProperty = "B";
        internal const string ErrorCodeProperty = "_ErrorCode";
        internal const string BreakCodeProperty = "_Break";
        internal const string SkipProperty = "_Skip";
        internal const string RequiredError = "Required";
        internal const string RegularExpressionError = "RegularExpression";

        [View(ViewControlType.Label)]
        public string SourceTable
        {
            get { return _sourceTable; }
            set { _sourceTable = value; }
        }
        private string _sourceTable;

        [View(ViewControlType.Label)]
        public virtual string SourceSQL
        {
            get { return _sourceSQL; }
            set { _sourceSQL = value; ; }
        }
        private string _sourceSQL;


        [XmlIgnore]
        internal Type SourceType
        {
            get 
            {
                return TempAssembly.GetType("Temp.SourceClass");

            }
        }

        [XmlIgnore]
        public Dictionary<string, PropertyInfo> SourcePropertyInfos
        {
            get 
            {
                if (_sourcePropertyInfos == null)
                {
                    _sourcePropertyInfos = SourceType.GetProperties().ToDictionary(x=>x.Name, x=>x);
                }
                return _sourcePropertyInfos; 
            }
        }
        private Dictionary<string,PropertyInfo> _sourcePropertyInfos;

        [ViewProperty(ViewControl.Properties.IsEnabled, "Idle")]
        public string BreakExpression
        {
            get { return _breakExpression; }
            set 
            { 
                SetValue("BreakExpression", out _breakExpression, value);
                _tempAssembly = null;
            }
        }
        private string _breakExpression;

        internal MethodInfo CompiledBreakExpression
        {
            get
            {
                if (string.IsNullOrEmpty(BreakExpression)) return null;
                return SourceType.GetProperty(BreakCodeProperty).GetAccessors().FirstOrDefault();
            }
        }

        [View(ViewControlType.Label)]
        public string TargetTable
        {
            get { return _targetTable; }
            set { _targetTable = value; }
        }
        private string _targetTable;

        public bool TargetTableAsTemp
        {
            get { return _targetTableAsTemp; }
            set { _targetTableAsTemp = value; }
        }
        private bool _targetTableAsTemp;


        [XmlIgnore]
        public List<string> SourceColumns
        {
            get { return _sourceColumns; }
            set { _sourceColumns = value; }
        }
        private List<string> _sourceColumns;

        [XmlIgnore]
        public List<string> TargetColumns
        {
            get { return _targetColumns; }
            set { _targetColumns = value; }
        }
        private List<string> _targetColumns;
        private List<string> _targetDbColumns;


        [XmlIgnore]
        internal Type TargetType
        {
            get 
            {
                return TempAssembly.GetType("Temp.TargetClass");
            }
        }
        private PropertyInfo skipProp;
        private PropertyInfo sourceProp;
        private PropertyInfo bufferProp;

        [XmlIgnore]
        public Dictionary<string, PropertyInfo> TargetPropertyInfos
        {
            get 
            {
                if (_targetPropertyInfos == null)
                {
                    _targetPropertyInfos = TargetType.GetProperties().ToDictionary(x => x.Name, x => x);
                }
                return _targetPropertyInfos; 
            }
        }
        private Dictionary<string, PropertyInfo> _targetPropertyInfos;

        [ViewProperty(ViewControl.Properties.IsEnabled, "Idle")]
        public bool TargetTruncate
        {
            get { return _targetTruncate; }
            set { SetValue("TargetTruncate", out _targetTruncate, value); }
        }
        private bool _targetTruncate;

        [ViewProperty(ViewControl.Properties.IsEnabled, "Idle")]
        public int? DegreeOfParallelism
        {
            get { return _degreeOfParallelism; }
            set { SetValue("DegreeOfParallelism", out _degreeOfParallelism, value); }
        }
        private int? _degreeOfParallelism;


        [View(ViewControlType.DataGrid)]
        [ViewProperty(DataGridViewControl.Properties.IsEnabled, "Idle")]
        [ViewProperty("CanUserAddRows",false)]
        public ObservableCollection<MappingItem> Mappings
        {
            get { return _mappings; }
        }
        private ObservableCollection<MappingItem> _mappings = new ObservableCollection<MappingItem>();

        [XmlIgnore]
        [View(ViewControlType.Label)]
        public TimeSpan ProcessTime
        {
            get { return _processTime; }
            set { SetValue("ProcessTime", out _processTime, value); }
        }
        private TimeSpan _processTime;


        [XmlIgnore]
        [View(ViewControlType.Label)]
        public int ProcessedCount
        {
            get { return _processedCount; }
            private set { SetValue("ProcessedCount", out _processedCount, value); }
        }
        private int _processedCount;

        [XmlIgnore]
        [View(ViewControlType.Label)]
        public int ErrorCount
        {
            get { return _errorCount; }
            private set { SetValue("ErrorCount", out _errorCount, value); }
        }
        private int _errorCount;

        [XmlIgnore]
        [View(ViewControlType.Label)]
        public int SkipCount
        {
            get { return _skipCount; }
            private set { SetValue("SkipCount", out _skipCount, value); }
        }
        private int _skipCount;

        [XmlIgnore]
        [View(ViewControlType.Label)]
        public int OutputCount
        {
            get { return _outputCount; }
            private set { SetValue("OutputCount", out _outputCount, value); }
        }
        private int _outputCount;

        [XmlIgnore]
        public RuleSet Rules
        {
            get
            {
                if (_rules == null)
                {
                    if (string.IsNullOrEmpty(RuleXml)) return null;
                    using (var st = new StringReader(RuleXml))
                    {
                        var serializer = new WorkflowMarkupSerializer();
                        using (var rulesReader = new XmlTextReader(st))
                        {
                            _rules = (RuleSet)serializer.Deserialize(rulesReader);
                        }
                    }
                }
                return _rules;
            }
        }
        private RuleSet _rules;

        [XmlIgnore]
        public RuleValidation RuleValidation
        {
            get
            {
                if (_ruleValidation == null)
                {
                    _ruleValidation = new RuleValidation(typeof(MappingRule<,>).MakeGenericType(SourceType, TargetType), null);
                }
                return _ruleValidation;
            }
        }
        private RuleValidation _ruleValidation;

        public string SourceConnectionString
        {
            get { return _sourceConnectionString; }
            set { this.SetValue("SourceConnectionString", out _sourceConnectionString, value); }
        }
        private string _sourceConnectionString;

        public string SourceDbProvider
        {
            get { return _sourceDbProvider; }
            set { this.SetValue("SourceDbProvider", out _sourceDbProvider, value); }
        }
        private string _sourceDbProvider;

        public string TargetConnectionString
        {
            get { return _targetConnectionString; }
            set { this.SetValue("TargetConnectionString", out _targetConnectionString, value); }
        }
        private string _targetConnectionString;

        public string TargetDbProvider
        {
            get { return _targetDbProvider; }
            set { this.SetValue("TargetDbProvider", out _targetDbProvider, value); }
        }
        private string _targetDbProvider;

        [Browsable(false)]
        [XmlIgnore]
        public bool IsBusy
        {
            get { return _isBusy; }
            private set { this.SetValue("IsBusy", out _isBusy, value); OnPropertyChanged("Idle"); }
        }
        private bool _isBusy;

        [Browsable(false)]
        [XmlIgnore]
        public bool Idle
        {
            get { return !_isBusy; }
        }

        [Browsable(false)]
        [XmlIgnore]
        public string Title
        {
            get { return FileName != null ? Path.GetFileNameWithoutExtension(FileName) : TargetTable; }
        }

        [Browsable(false)]
        [XmlIgnore]
        public string FileName
        {
            get { return _fileName; }
            set { this.SetValue("FileName", out _fileName, value); OnPropertyChanged("Title"); }
        }
        internal string _fileName;

        [Display(Name = "Execute")]
        [XmlIgnore]
        public ICommand ExecuteCommand { get { return CreateCommand(Execute, (x)=>Idle); } }

        [Display(Name = "Edit Rule")]
        [XmlIgnore]
        [Browsable(false)]
        public ICommand EditRuleSetCommand { get { return CreateCommand(EditRuleSet, (x) => Idle); } }

        [Display(Name = "Remove Rule")]
        [XmlIgnore]
        [Browsable(false)]
        public ICommand RemoveRuleSetCommand { get { return CreateCommand(() => { this.RuleXml = null;  this._rules = null; }, (x) => Idle && this._rules != null); } }

        [Display(Name = "Export Data")]
        [XmlIgnore]
        [Browsable(false)]
        public ICommand ExportDataCommand { get { return CreateCommand(Export, (x) => Idle); } }


        [Display(Name = "Drop Temp Table")]
        [XmlIgnore]
        public ICommand DropTempTableCommand { get { return CreateCommand(DropTargetTable, (x) => Idle && TargetTableAsTemp); } }


        [Display(Name = "Cancel")]
        [XmlIgnore]
        public ICommand CancelCommand { get { return CreateCommand(Cancel, (x) => IsBusy); } }

        public object Content 
        { 
            get 
            {
                if (_content == null)
                {
                    _content=ViewUtil.BuildContent(this); 
                }
                return _content;
            } 
        }
        private object _content;

        public MappingVM()
        {
        }

        public MappingVM(MainVM parent)
        {
            this.SourceTable = parent.SourceTable;
            this.SourceSQL = parent.SourceSQL;
            this.TargetTable = parent.TargetTableName;

            this.SourceDbProvider = parent.SourceDbProvider;
            this.SourceConnectionString = parent.SourceConnectionString;
            this.TargetDbProvider = parent.TargetDbProvider;
            this.TargetConnectionString = parent.TargetConnectionString;

            Init();

            foreach (var targetColumn in this.TargetColumns)
            {
                var map = new MappingItem();
                map._parent = this;
                map.TargetColumn = targetColumn;
                if (this.SourceColumns.Contains(targetColumn))
                {
                    map.SourceColumn = targetColumn;
                }
                Mappings.Add(map);
            }
        }

        public void Init()
        {

            this.SourceColumns = DbProviderRepository.GetDbSchemaProvider(SourceDbProvider, SourceConnectionString)
                .GetTableColumn(GetSourceCommandText()).Select(x=>x.Key).OrderBy(x => x).ToList();
            this.SourceColumns.Insert(0, "");
            this.SourceColumns.Add(ErrorCodeProperty);

            var targetProvider =  DbProviderRepository.GetDbSchemaProvider(TargetDbProvider, TargetConnectionString);
            if (!IsExsitsTargetTable())
            {
                if (TargetConnectionString != SourceConnectionString) throw new NotSupportedException("different connection string");
                CreateTempTable();
                TargetTableAsTemp = true;
            }
            this._targetDbColumns = targetProvider.GetTableColumn(this.TargetTable).Select(x => x.Key).OrderBy(x => x).ToList();
            this.TargetColumns = _targetDbColumns.ToList();
            this.TargetColumns.Add(SkipProperty);
            foreach (var item in Mappings)
            {
                item._parent = this;
            }
        }

        private void EditRuleSet()
        {
            CacheClear();
            var ctxType = typeof(MappingRule<,>).MakeGenericType(SourceType, TargetType);
            var ruleSetDialog = new RuleSetDialog(ctxType, null, Rules);
            ruleSetDialog.ShowDialog();
            if (ruleSetDialog.RuleSet == null) return;
            _rules = ruleSetDialog.RuleSet;

            var fileName = SourceTable + "_" + TargetTable + ".xml";
            using (var st = new StringWriter())
            {
                var rulesWriter = new XmlTextWriter(st);
                var serializer = new WorkflowMarkupSerializer();
                serializer.Serialize(rulesWriter, ruleSetDialog.RuleSet);
                rulesWriter.Close();
                RuleXml = st.ToString();
            }
        }

        [Browsable(false)]
        public string RuleXml { get; set; }

        private void Truncate()
        {
            var db = DbProviderRepository.GetDbSchemaProvider(TargetDbProvider, TargetConnectionString);
            db.Truncate(TargetTable);
        }

        private void CreateTempTable()
        {
            var db = DbProviderRepository.GetDbSchemaProvider(TargetDbProvider, TargetConnectionString);
            db.CreateTempTable(TargetTable, GetSourceCommandText());
        }

        private void DropTargetTable()
        {
            if (!IsExsitsTargetTable()) return;
            var db = DbProviderRepository.GetDbSchemaProvider(TargetDbProvider, TargetConnectionString);
            using (var cnct = db.Factory.CreateConnection())
            {
                cnct.ConnectionString = db.Config.ConnectionString;
                cnct.Open();
                var cmd = cnct.CreateCommand();
                cmd.CommandText = string.Format("drop table {0}", db.GetTableName(TargetTable));
                cmd.ExecuteNonQuery();
            }
        }

        private void Export()
        {
            if (!IsExsitsTargetTable()) return;
            var db = DbProviderRepository.GetDbSchemaProvider(SourceDbProvider, SourceConnectionString);
            using (var cnct = db.Factory.CreateConnection())
            {
                cnct.ConnectionString = db.Config.ConnectionString;
                cnct.Open();
                var cmd = cnct.CreateCommand();
                cmd.CommandText = GetSourceCommandText(); // string.Format("select * from {0} ", db.GetTableName(SourceTable));
                using (var reader = cmd.ExecuteReader())
                {
                    using (var csv = this.ShowSaveTextFile("*.txt|*.txt"))
                    {
                        if (csv != null)
                        {
                            var header = new List<string>();
                            for (int i = 0; i < reader.FieldCount; i++)
                            {
                                header.Add(reader.GetName(i));
                            }
                            csv.WriteLine(string.Join("\t", header));
                            while (reader.Read())
                            {
                                var values = new List<string>();
                                for (int i = 0; i < reader.FieldCount; i++)
                                {
                                    var val = reader.GetValue(i);
                                    if (val == null)
                                    {
                                        values.Add("");
                                    }
                                    else
                                    {
                                        values.Add(val.ToString());
                                    }
                                }
                                csv.WriteLine(string.Join("\t", values));
                            }
                        }
                    }
                }
            }
        }


        private bool IsExsitsTargetTable()
        {
            var db = DbProviderRepository.GetDbSchemaProvider(TargetDbProvider, TargetConnectionString);
            using (var cnct = db.Factory.CreateConnection())
            {
                cnct.ConnectionString = db.Config.ConnectionString;
                cnct.Open();
                var cmd = cnct.CreateCommand();
                cmd.CommandText = string.Format("select * from {0} where 1=0", db.GetTableName(TargetTable));
                try
                {
                    cmd.ExecuteNonQuery();
                    return true;
                }
                catch
                {
                    return false;
                }
            }

        }

        private void CacheClear()
        {
            _sourcePropertyInfos = null;
            _targetPropertyInfos = null;
            _ruleValidation = null;
            _tempAssembly = null;
        }

        private BackgroundWorker worker;
        private Exception workerException;
        private Stopwatch workerStopWatch;
        private int workerDataCount;
        private DateTime LastReportTime;

        private void Execute()
        {
            worker = new BackgroundWorker();
            worker.WorkerReportsProgress = true;
            worker.WorkerSupportsCancellation = true;
            worker.DoWork += worker_DoWork;
            worker.ProgressChanged += worker_ProgressChanged;
            worker.RunWorkerCompleted += worker_RunWorkerCompleted;
            workerException = null;
            IsBusy = true;
            worker.RunWorkerAsync();
        }

        void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                ConvertDb();
            }
            catch (Exception ex)
            {
                Trace.WriteLine(ex.ToString());
                workerException = ex;
            }

        }

        private string GetSourceCommandText()
        {
            var db = DbProviderRepository.GetDbSchemaProvider(SourceDbProvider, SourceConnectionString);

            if (string.IsNullOrEmpty(SourceSQL))
            {
                return string.Format("select * from {0}", db.GetTableName(SourceTable));
            }
            else if (SourceSQL.Trim().StartsWith("@"))
            {
                var fileName = SourceSQL.Trim().Substring(1);
                return File.ReadAllText(fileName);
            }
            else
            {
                return SourceSQL;
            }
        }

        public void ConvertDb()
        {
            CacheClear();

            var sourceProvider = DbProviderRepository.GetDbSchemaProvider(SourceDbProvider, SourceConnectionString);
            var targetProvider = DbProviderRepository.GetDbSchemaProvider(TargetDbProvider, TargetConnectionString);

            var targetExsists = IsExsitsTargetTable();
            if (targetExsists && TargetTableAsTemp)
            {
                DropTargetTable();
                targetExsists = false;
            }
            if (!targetExsists) CreateTempTable();
            
            var ctxType = typeof(MappingRule<,>).MakeGenericType(SourceType, TargetType);

            if (this.TargetTruncate) Truncate();

            skipProp = TargetType.GetProperty(SkipProperty);
            bufferProp = TargetType.GetProperty(BufferProperty);
            sourceProp = TargetType.GetProperty(SourceProperty);

            workerDataCount = 0;
            ErrorCount = 0;
            SkipCount = 0;
            OutputCount = 0;
            LastReportTime = DateTime.Now;

            workerStopWatch = new Stopwatch();
            workerStopWatch.Start();
            using (var sourceConnection = sourceProvider.Factory.CreateConnection())
            {
                sourceConnection.ConnectionString = sourceProvider.Config.ConnectionString;
                var command = sourceConnection.CreateCommand();
                if (string.IsNullOrEmpty(SourceSQL))
                {
                    command.CommandText = string.Format("select * from {0}", sourceProvider.GetTableName(SourceTable));
                }
                else
                {
                    command.CommandText = SourceSQL;
                }
                command.CommandTimeout = 0;
                sourceConnection.Open();
                var reader = command.ExecuteReader();
                var pipeline = AsEnumerable(reader);

                if (CompiledBreakExpression != null)
                {
                    var buffedPipeline = Buffer(pipeline);
                    if (DegreeOfParallelism.HasValue)
                    {
                        pipeline = Parallel(buffedPipeline).SelectMany(x => Flatten(x, ctxType));
                    }
                    else
                    {
                        pipeline = buffedPipeline.SelectMany(x => Flatten(x, ctxType));
                    }

                }
                else
                {
                    if (DegreeOfParallelism.HasValue)
                    {
                        pipeline = Parallel(pipeline).Select(x => Context(x, ctxType));
                    }
                    else
                    {
                        pipeline = pipeline.Select(x => Context(x, ctxType));
                    }
                }
                pipeline = pipeline.Where(x => !IsSkip(x));
                BulkCopy(Cast(pipeline));
            }
            workerStopWatch.Stop();
            this.ProcessTime = workerStopWatch.Elapsed;
        }

        private ParallelQuery<T> Parallel<T>(IEnumerable<T> pipeline)
        {
            if (DegreeOfParallelism.Value > 0)
            {
                return pipeline.AsParallel().WithDegreeOfParallelism(DegreeOfParallelism.Value);
            }
            else
            {
                return pipeline.AsParallel();
            } 
        }

        void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            ProcessedCount = workerDataCount;
            ProcessTime = workerStopWatch.Elapsed;
        }

        void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            IsBusy = false;
            ProcessedCount = workerDataCount;
            if (workerStopWatch != null) ProcessTime = workerStopWatch.Elapsed;
            if (workerException != null)
            {
                if (workerException is TargetInvocationException && workerException.InnerException != null)
                {
                    workerException = workerException.InnerException;
                    if ((workerException is TargetInvocationException || workerException is AggregateException) && workerException.InnerException != null)
                    {
                        workerException = workerException.InnerException;
                    }
                }
                this.ShowMessage(workerException.Message);
            }
        }

        internal void Cancel()
        {
            worker.CancelAsync();
        }

        private IEnumerable<object> AsEnumerable(IDataReader reader)
        {
            var method = typeof(LinqExtension).GetMethod("AsEnumerable").MakeGenericMethod(SourceType);
            return (IEnumerable<object>)method.Invoke(null, new object[] {reader});
        }

        private IEnumerable<object> Cast(IEnumerable<object> source)
        {
            var method = typeof(Enumerable).GetMethod("Cast").MakeGenericMethod(TargetType);
            return (IEnumerable<object>)method.Invoke(null, new object[] { source });
        }

        private IEnumerable<IEnumerable> Buffer(IEnumerable<object> source)
        {
            object preBreakKey = null;
            Type listType = typeof(List<>).MakeGenericType(SourceType);
            IList buffer = (IList)Activator.CreateInstance(listType);
            PropertyInfo bufferProp = SourceType.GetProperty(BufferProperty);
            foreach (var item in source)
            {
                bufferProp.SetValue(item, buffer, null);
                object breakKey = CompiledBreakExpression.Invoke(item, null);
                if (preBreakKey != null && !(object.Equals(breakKey, preBreakKey)))
                {
                    yield return buffer;
                    buffer = (IList)Activator.CreateInstance(listType);
                }
                buffer.Add(item);
                preBreakKey = breakKey;
            }
            if (buffer.Count > 0) yield return buffer;
        }

        private IEnumerable<object> Flatten(IEnumerable item, Type ctxType)
        {
            foreach (var itemItem in item)
            {
                var obj = (IMappingRule)Activator.CreateInstance(ctxType);
                obj.Source = itemItem;
                obj.Buffer = item;
                yield return MappingItem(obj);
            }
        }

        private object Context(object item, Type ctxType)
        {
            var obj = (IMappingRule)Activator.CreateInstance(ctxType);
            obj.Source = item;
            return MappingItem(obj);
        }

        [ThreadStatic]
        private int ThreadId;

        private object MappingItem(IMappingRule item)
        {
            var targetObject = Activator.CreateInstance(TargetType);
            sourceProp.SetValue(targetObject, item.Source, null);
            bufferProp.SetValue(targetObject, item.Buffer, null);
            foreach (var map in this.Mappings)
            {

                try
                {
                    object sourceValue = null;
                    if (!string.IsNullOrEmpty(map.SourceColumn)) sourceValue = SourcePropertyInfos[GetPropName(map.SourceColumn)].GetValue(item.Source, null);
                    CheckData(item.Source, map, sourceValue);
                    if (map.AutoTypeConvert) sourceValue = ConvertType(map, sourceValue);
                    if (string.IsNullOrWhiteSpace(map.Expression) && TargetPropertyInfos[GetPropName(map.TargetColumn)].CanWrite)
                    {
                        TargetPropertyInfos[GetPropName(map.TargetColumn)].SetValue(targetObject, sourceValue, null);
                    }
                }
                catch (Exception ex)
                {
                    var targetT = typeof(object);
                    if (TargetPropertyInfos.ContainsKey(GetPropName(map.TargetColumn))) targetT = TargetPropertyInfos[GetPropName(map.TargetColumn)].PropertyType;
                    throw new InvalidExpressionException(ex.Message + "\n\nTarget Column:" + map.TargetColumn );
                }
            }
            item.Target = targetObject;
            if (this.Rules != null)
            {
                var execution = new RuleExecution(RuleValidation, item);
                this.Rules.Execute(execution);
            }
            return targetObject;
        }

        private bool IsSkip(object arg)
        {
            if (ThreadId != Thread.CurrentThread.ManagedThreadId)
            {
                ThreadId = Thread.CurrentThread.ManagedThreadId;
                Debug.WriteLine("Thread Id:" + ThreadId.ToString());
            }

            workerDataCount++;
            if (worker != null && DateTime.Now.Subtract(LastReportTime).TotalMilliseconds > 100)
            {
                if (worker.CancellationPending) throw new OperationCanceledException();
                worker.ReportProgress(workerDataCount);
                LastReportTime = DateTime.Now;
            }

            bool skip = (bool)skipProp.GetValue(arg, null);
            if (skip)
            {
                SkipCount++;
            }
            else
            {
                OutputCount++;
            }
            return skip;
        }

        private object ConvertType(MappingItem map, object sourceValue)
        {
            var targetType = TargetPropertyInfos[GetPropName(map.TargetColumn)].PropertyType;
            if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                if (sourceValue == null) return null;
                return Convert.ChangeType(sourceValue, targetType.GetGenericArguments()[0]);

            }
            else
            {
                return Convert.ChangeType(sourceValue, TargetPropertyInfos[GetPropName(map.TargetColumn)].PropertyType);
            }
        }

        private void CheckData(object item, DbStreamConverter.MappingItem map, object sourceValue)
        {
            var errors = new List<string>();
            if (sourceValue == null && map.IsRequired) errors.Add(RequiredError);
            if (map.RegularExpressionRule != null && !map.RegularExpressionRule.IsValid(sourceValue)) errors.Add(RegularExpressionError);
            if (errors.Count > 0)
            {
                SourcePropertyInfos[ErrorCodeProperty].SetValue(item, string.Join(",", errors), null);
                ErrorCount++;
            }
        }

        private IEnumerable<object> BulkCopy(IEnumerable<object> source)
        {
            var method = typeof(LinqExtension).GetMethod("BulkCopy").MakeGenericMethod(TargetType);
            return (IEnumerable<object>)method.Invoke(null, new object[] { source, TargetTable, TargetDbProvider, TargetConnectionString, 
                TargetPropertyInfos.Keys.Where(x=> OutputColumn(x)).ToList(), 0 });
        }

        private bool OutputColumn(string propName)
        {
            return (this._targetDbColumns.Contains(propName));
        }

        private string GetPropName(string colName)
        {
            return colName.Replace(" ","_").Replace("[","").Replace("\"","");
        }
        public Assembly TempAssembly
        {
            get 
            {
                if (_tempAssembly == null)
                {
                    var sourceClass = new ClassCode();
                    sourceClass.Name = "SourceClass";
                    foreach (var prop in DbProviderRepository.GetDbSchemaProvider(SourceDbProvider, SourceConnectionString).GetTableColumn(GetSourceCommandText()))
                    {
                        sourceClass.PropList.Add(new PropCode { Name = GetPropName(prop.Key), PropType = TypeUtil.GetTypeString(prop.Value) });
                    }
                    sourceClass.PropList.Add(new PropCode { Name = BreakCodeProperty, PropType = "object", Code = this.BreakExpression});
                    sourceClass.PropList.Add(new PropCode { Name = BufferProperty, PropType = "List<SourceClass>" });
                    sourceClass.PropList.Add(new PropCode { Name = ErrorCodeProperty, PropType = "string" });

                    var targetClass = new ClassCode();
                    targetClass.Name = "TargetClass";
                    targetClass.PropList.Add(new PropCode { Name = SourceProperty, PropType = "SourceClass" });
                    targetClass.PropList.Add(new PropCode { Name = BufferProperty, PropType = "List<SourceClass>" });
                    foreach (var prop in DbProviderRepository.GetDbSchemaProvider(TargetDbProvider, TargetConnectionString).GetTableColumn(this.TargetTable))
                    {
                        var code = Mappings.Where(x => x.TargetColumn == prop.Key).Select(x => x.Expression).FirstOrDefault();
                        targetClass.PropList.Add(new PropCode { Name = GetPropName(prop.Key), PropType = TypeUtil.GetTypeString(prop.Value), Code = code});
                    }
                    var skipCode = Mappings.Where(x => x.TargetColumn == SkipProperty).Select(x => x.Expression).FirstOrDefault();
                    targetClass.PropList.Add(new PropCode { Name = SkipProperty, PropType = "bool", Code = skipCode });
                    _tempAssembly = TypeUtil.CreateTempAssembly(new ClassCode[] { sourceClass, targetClass });
                }
                return _tempAssembly; 
            }
        }

        private System.Reflection.Assembly _tempAssembly;




    }

    public class MappingItem : ViewModelBase
    {
        internal MappingVM _parent;

        public MappingItem()
        {
        }


        [XmlIgnore]
        public List<string> SourceColumns
        {
            get { return _parent.SourceColumns; }
        }

        [XmlIgnore]
        public List<string> TargetColumns
        {
            get { return _parent.TargetColumns; }
        }

        [View(ViewControlType.DataGridComboBoxColumn)]
        [ViewProperty(DataGridComboBoxColumnViewElement.Properties.ItemsSource, "SourceColumns")]
        public string SourceColumn
        {
            get { return _sourceColumn; }
            set
            {
                SetValue("SourceColumn", out _sourceColumn, value);
            }
        }
        private string _sourceColumn;


        public bool IsRequired
        {
            get { return _isRequired; }
            set { SetValue("IsRequired", out _isRequired, value); }
        }
        private bool _isRequired;

        public string RegularExpression
        {
            get { return _regularExpression; }
            set 
            { 
                SetValue("RegularExpression", out _regularExpression, value);
                if (string.IsNullOrEmpty(value))
                {
                    _regularExpressionRule =null;
                }
                else
                {
                    _regularExpressionRule = new RegularExpressionAttribute(value);
                }
            }
        }
        private string _regularExpression;

        internal RegularExpressionAttribute RegularExpressionRule
        {
            get 
            {
                return _regularExpressionRule; 
            }
        }
        private RegularExpressionAttribute _regularExpressionRule;
 
        public string Expression
        {
            get { return _expression; }
            set 
            { 
                SetValue("Expression", out _expression, value);
            }
        }
        private string _expression;


        [Display(Name="Type Convert")]
        public bool AutoTypeConvert
        {
            get { return _autoTypeConvert; }
            set { SetValue("AutoTypeConvert", out _autoTypeConvert, value); }
        }
        private bool _autoTypeConvert;


        [View(ViewControlType.DataGridTextColumn)]
        [ViewProperty(DataGridTextColumnViewElement.Properties.IsReadOnly, true)]
        public string TargetColumn { get; set; }


    }

}
